﻿// -----------------------------------------------------------------------
// <copyright file="StatesManager.cs" company="Microsoft">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace RRSync.Lib
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using RRSync.Lib.Models;
    using System.Runtime.Serialization;
    using System.IO;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Collections;
    using System.Security.Cryptography;
    using System.Diagnostics;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class FileStatesManager : IFileStatesManager
    {
        private static IFileStatesManager instance;

        private FileStatesManager()
        {
            this.Stopwatch = new Stopwatch();
            this.FileStatesCache = new FileStatesModel();
        }

        public static IFileStatesManager Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new FileStatesManager();
                }

                return instance;
            }
        }

        public FileStatesModel FileStatesCache { get; private set; }

        public Stopwatch Stopwatch { get; private set; }

        public event StateChangesEventHandler StateChanged;

        public void LoadStates(string filepath)
        {
            Console.WriteLine("\n\nLoading...");
            this.Stopwatch.Start();

            Stream stream = null;
            try
            {
                stream = File.Open(filepath, FileMode.Open);
                BinaryFormatter bFormatter = new BinaryFormatter();
                this.FileStatesCache = (FileStatesModel)bFormatter.Deserialize(stream);
                Console.WriteLine("Load Succeed.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Load Failed: {0}", ex.Message);
            }
            finally
            {
                stream.Close();
            }

            this.Stopwatch.Stop();
            Console.WriteLine("Elapsed time {0}", string.Format("{0}:{1}", Math.Floor(this.Stopwatch.Elapsed.TotalMinutes), this.Stopwatch.Elapsed.ToString("ss\\.ff")));
            this.Stopwatch.Reset();
        }

        public void SaveStates(string filepath)
        {
            Console.WriteLine("\n\nSaving...");
            this.Stopwatch.Start();

            Stream stream = null;
            try
            {
                stream = File.Open(filepath, FileMode.Create);
                BinaryFormatter bFormatter = new BinaryFormatter();
                bFormatter.Serialize(stream, this.FileStatesCache);
                Console.WriteLine("Save Succeed.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to save: {0}", ex.Message);
            }
            finally
            {
                stream.Close();
            }

            this.Stopwatch.Stop();
            Console.WriteLine("Elapsed time {0}", string.Format("{0}:{1}", Math.Floor(this.Stopwatch.Elapsed.TotalMinutes), this.Stopwatch.Elapsed.ToString("ss\\.ff")));
            this.Stopwatch.Reset();
        }


        public void Sync(string directoryPath)
        {
            Console.WriteLine("\n\nSynchronizing...");
            this.Stopwatch.Start();

            var token = Guid.NewGuid();
            try 
	        {
                var files = Directory.GetFiles(directoryPath);

                if(files != null)
                {
                    for (int i = 0; i < files.Length; i++)
                    {
                        try
                        {
                            var fileState = this.FileStatesCache.FileStates[files[i]];
                            var lastModified = File.GetLastWriteTime(files[i]);

                            if (lastModified != fileState.LastModified)
                            {
                                fileState.LastModified = lastModified;

                                // file's updated
                                var hashValue = GetHash(files[i]);

                                if (hashValue != fileState.Hash)
                                {
                                    // file's changed/different
                                    fileState.Hash = hashValue;

                                    if (StateChanged != null)
                                    {
                                        StateChangesEventArgs eventArgs = new StateChangesEventArgs
                                        {
                                            FileName = files[i],
                                            FileState = fileState,
                                            ChangeType = ChangeTypes.Update
                                        };

                                        StateChanged(this, eventArgs);
                                    }
                                }
                            }

                            fileState.TokenProcess = token;
                        }
                        catch (KeyNotFoundException ex)
                        {
                            // new file
                            var fileState = new FileStateModel();

                            fileState.LastModified = File.GetLastWriteTime(files[i]);
                            fileState.Hash = GetHash(files[i]);
                            fileState.TokenProcess = token;

                            this.FileStatesCache.FileStates.Add(files[i], fileState);

                            if (StateChanged != null)
                            {
                                StateChangesEventArgs eventArgs = new StateChangesEventArgs
                                {
                                    FileName = files[i],
                                    FileState = fileState,
                                    ChangeType = ChangeTypes.Create
                                };

                                StateChanged(this, eventArgs);
                            }
                        }
                    }

                    var deletedFiles = new List<KeyValuePair<string, FileStateModel>>(this.FileStatesCache.FileStates.Where(s => s.Value.TokenProcess != token));

                    if (deletedFiles != null && StateChanged != null)
                    {
                        foreach (var file in deletedFiles)
                        {
                            StateChangesEventArgs eventArgs = new StateChangesEventArgs
                            {
                                FileName = file.Key,
                                FileState = file.Value,
                                ChangeType = ChangeTypes.Delete
                            };

                            this.FileStatesCache.FileStates.Remove(file.Key);

                            StateChanged(this, eventArgs);
                        }
                    }
                }
                Console.WriteLine("Synchronization Succeed.");
	        }
	        catch (Exception ex)
	        {
                Console.WriteLine("Failed to sync: {0}", ex.Message);
            }

            this.Stopwatch.Stop();
            Console.WriteLine("Elapsed time {0}", string.Format("{0}:{1}", Math.Floor(this.Stopwatch.Elapsed.TotalMinutes), this.Stopwatch.Elapsed.ToString("ss\\.ff")));
            this.Stopwatch.Reset();
        }

        private string GetHash(string filePath)
        {
            using (FileStream file = new FileStream(filePath, FileMode.Open))
            {
                MD5 md5 = new MD5CryptoServiceProvider();

                return Convert.ToBase64String(md5.ComputeHash(file));
            }
        }

        public void GenerateFileStates(string directoryPath)
        {
            Console.WriteLine("\n\nGenerating File States...");
            this.Stopwatch.Start();

            try
            {
                var files = Directory.GetFiles(directoryPath);

                if (files != null)
                {
                    this.FileStatesCache.FileStates.Clear();

                    for (int i = 0; i < files.Length; i++)
                    {
                        var fileState = new FileStateModel();
                        fileState.LastModified = File.GetLastWriteTime(files[i]);
                        fileState.Hash = GetHash(files[i]);

                        this.FileStatesCache.FileStates.Add(files[i], fileState);

                        if (i > 0 && (i % 1000) == 0)
                        {
                            Console.WriteLine("{0} File states loaded. And there are more...", i);
                        }
                    }
                }
                Console.WriteLine("Generate File States Succeed. {0} file states has been loaded.");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to Generate File State: {0}", ex.Message);
            }

            this.Stopwatch.Stop();
            Console.WriteLine("Elapsed time {0}", string.Format("{0}:{1}", Math.Floor(this.Stopwatch.Elapsed.TotalMinutes), this.Stopwatch.Elapsed.ToString("ss\\.ff")));
            this.Stopwatch.Reset();
        }
    }
}
